package com.ideaaedi.springcloud.jd.user.listener;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import com.google.common.collect.Sets;
import com.ideaaedi.commonspring.annotation.RecordParameters;
import com.ideaaedi.springcloud.jd.commonds.enums.CachePrefixEnum;
import com.ideaaedi.springcloud.jd.commonds.enums.user.DataScopeTypeEnum;
import com.ideaaedi.springcloud.jd.commonds.enums.user.UserTypeEnum;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysDeptAddedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysDeptDeletedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysDeptUpdatedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysMenuBatchDeletedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysMenuDeletedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysMenuUpdatedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysPostDeletedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysRoleDeletedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysUserAddedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysUserDeletedEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysUserLoginSuccessEvent;
import com.ideaaedi.springcloud.jd.commonds.event.user.SysUserUpdatedEvent;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysBackendUserPO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysDataScopePO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysDeptPO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysDeptPathTracePO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysMenuApiRePO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysMenuPO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysPostPO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysRoleApiRePO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysRoleMenuRePO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysRolePO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysTenantPO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysUserDeptPostRePO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysUserPO;
import com.ideaaedi.springcloud.jd.commonds.entity.user.po.SysUserRoleRePO;
import com.ideaaedi.springcloud.jd.commonds.enums.POSubstate;
import com.ideaaedi.springcloud.jd.commonds.util.CommonUtil;
import com.ideaaedi.springcloud.jd.commonspring.config.properties.TenantProperties;
import com.ideaaedi.springcloud.jd.commonspring.support.AuthService;
import com.ideaaedi.springcloud.jd.user.service.SysBackendUserService;
import com.ideaaedi.springcloud.jd.user.service.SysDataScopeService;
import com.ideaaedi.springcloud.jd.user.service.SysDeptPathTraceService;
import com.ideaaedi.springcloud.jd.user.service.SysDeptService;
import com.ideaaedi.springcloud.jd.user.service.SysMenuApiReService;
import com.ideaaedi.springcloud.jd.user.service.SysMenuService;
import com.ideaaedi.springcloud.jd.user.service.SysRoleApiReService;
import com.ideaaedi.springcloud.jd.user.service.SysRoleMenuReService;
import com.ideaaedi.springcloud.jd.user.service.SysTenantService;
import com.ideaaedi.springcloud.jd.user.service.SysUserDeptPostReService;
import com.ideaaedi.springcloud.jd.user.service.SysUserRoleReService;
import com.ideaaedi.springcloud.jd.user.util.SysUserUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.event.EventListener;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;

/**
 * 同步事件监听器
 *
 * @author <font size = "20" color = "#3CAA3C"><a href="https://gitee.com/JustryDeng">JustryDeng</a></font> <img
 * src="https://gitee.com/JustryDeng/shared-files/raw/master/JustryDeng/avatar.jpg" />
 * @since 2021.0.5.B
 */
@Slf4j
@Component
@RecordParameters
public class SyncListener {
    
    @Resource
    private AuthService authService;
    
    @Resource
    private SysDeptService sysDeptService;
    
    @Resource
    private SysMenuApiReService sysMenuApiReService;
    
    @Resource
    private SysMenuService sysMenuService;
    
    @Resource
    private SysRoleApiReService sysRoleApiReService;
    
    @Resource
    private SysRoleMenuReService sysRoleMenuReService;
    
    @Resource
    private SysTenantService sysTenantService;
    
    @Resource
    private SysDataScopeService sysDataScopeService;
    
    @Resource
    private SysBackendUserService sysBackendUserService;
    
    @Resource
    private SysUserDeptPostReService sysUserDeptPostReService;
    
    @Resource
    private SysUserRoleReService sysUserRoleReService;
    
    @Resource
    private TenantProperties tenantProperties;
    
    @Resource
    private StringRedisTemplate stringRedisTemplate;
    
    @Resource
    private SysDeptPathTraceService sysDeptPathTraceService;
    
    @EventListener
    public void handleSysDeptAddedEvent(SysDeptAddedEvent event) {
        SysDeptPO addedDept = event.getAddedDept();
        String deptPath = addedDept.getPath();
        // 租户信息同步
        if (SysUserUtil.isAsBizTenant(deptPath, tenantProperties.getBizTenantLevel())) {
            sysTenantService.createIfMiss(Sets.newHashSet(addedDept.getId()));
        }
    }
    
    @EventListener
    public void handleSysDeptDeletedEvent(SysDeptDeletedEvent event) {
        SysDeptPO deletedDept = event.getDeletedDept();
        Integer deptId = deletedDept.getId();
        // 租户信息同步
        sysTenantService.removeIfExist(Sets.newHashSet(deptId));
        // 同时也删除部门和人员的关联关系
        sysUserDeptPostReService.remove(new LambdaQueryWrapper<SysUserDeptPostRePO>().eq(SysUserDeptPostRePO::getDeptId, deptId));
        // 将删除的部门路径添加到历史表中
        SysDeptPathTracePO sysDeptPathTrace = new SysDeptPathTracePO();
        sysDeptPathTrace.setDeptId(deptId);
        sysDeptPathTrace.setDeptPath(deletedDept.getPath());
        sysDeptPathTraceService.save(sysDeptPathTrace);
    }
    
    @EventListener
    public void handleSysDeptUpdatedEvent(SysDeptUpdatedEvent event) {
        SysDeptPO oldDept = event.getOldDept();
        SysDeptPO newDept = event.getNewDept();
        Integer deptId = oldDept.getId();
        String oldPath = oldDept.getPath();
        String newPath = newDept.getPath();
        if (newPath.equals(oldPath)) {
            return;
        }
        // section1. 更新子部门的path及tenant
        List<SysDeptPO> childList = sysDeptService.list(new LambdaQueryWrapper<SysDeptPO>().select(SysDeptPO::getId,
                SysDeptPO::getTenant, SysDeptPO::getPath).likeRight(SysDeptPO::getPath, oldPath + ","));
        for (SysDeptPO child : childList) {
            String oldChildPath = child.getPath();
            String newChildPath = newPath + oldChildPath.substring(oldPath.length());
            child.setPath(newChildPath);
            child.setTenant(newChildPath);
        }
        sysDeptService.updateBatchById(childList);
        
        // section2. 租户信息同步
        Set<Integer> asTenantDeptIdSet = Sets.newHashSet();
        if (SysUserUtil.isAsBizTenant(newPath, tenantProperties.getBizTenantLevel())) {
            asTenantDeptIdSet.add(deptId);
        }
        for (SysDeptPO sysDept : childList) {
            if (SysUserUtil.isAsBizTenant(sysDept.getPath(), tenantProperties.getBizTenantLevel())) {
                asTenantDeptIdSet.add(sysDept.getId());
            }
        }
        // 所有租户的deptId
        Set<Integer> dbAllTenantDeptIdSet =
                sysTenantService.list(new LambdaQueryWrapper<SysTenantPO>().select(SysTenantPO::getDeptId)).stream().filter(x -> x != null && x.getDeptId() != null).map(SysTenantPO::getDeptId).collect(Collectors.toSet());
        // 本次修改部门所涉及到的所有部门id
        Set<Integer> maybeHasChangeDeptIdSet = Sets.newHashSet(deptId);
        maybeHasChangeDeptIdSet.addAll(childList.stream().map(SysDeptPO::getId).collect(Collectors.toSet()));
        // 本次修改部门所涉及到的作为租户的部门id
        maybeHasChangeDeptIdSet.retainAll(dbAllTenantDeptIdSet);
        // 新增的
        sysTenantService.createIfMiss(asTenantDeptIdSet);
        // 移除的
        maybeHasChangeDeptIdSet.removeAll(asTenantDeptIdSet);
        sysTenantService.removeIfExist(maybeHasChangeDeptIdSet);
        
        // section3. 记录部门oldDept去sys_dept_path_his表
        SysDeptPathTracePO sysDeptPathHis = new SysDeptPathTracePO();
        sysDeptPathHis.setDeptId(deptId);
        sysDeptPathHis.setDeptPath(oldPath);
        sysDeptPathTraceService.save(sysDeptPathHis);
    }
    
    @EventListener
    public void handleSysMenuBatchDeletedEvent(SysMenuBatchDeletedEvent event) {
        List<SysMenuPO> deletedMenuList = event.getDeletedMenuList();
        List<Long> deletedMenuIdSet = deletedMenuList.stream().map(SysMenuPO::getId).collect(Collectors.toList());
        // 删除菜单关联的api
        sysMenuApiReService.remove(new LambdaQueryWrapper<SysMenuApiRePO>().in(SysMenuApiRePO::getMenuId,
                deletedMenuIdSet));
        // 删除菜单关联的角色
        sysRoleMenuReService.remove(new LambdaQueryWrapper<SysRoleMenuRePO>().in(SysRoleMenuRePO::getMenuId,
                deletedMenuIdSet));
    }
    
    @EventListener
    public void handleSysMenuDeletedEvent(SysMenuDeletedEvent event) {
        // 删除菜单关联的api
        Long menuId = event.getDeletedMenu().getId();
        sysMenuApiReService.remove(new LambdaQueryWrapper<SysMenuApiRePO>().eq(SysMenuApiRePO::getMenuId, menuId));
        // 删除菜单关联的角色
        sysRoleMenuReService.remove(new LambdaQueryWrapper<SysRoleMenuRePO>().eq(SysRoleMenuRePO::getMenuId, menuId));
    }
    
    @EventListener
    public void handleSysMenuUpdatedEvent(SysMenuUpdatedEvent event) {
        SysMenuPO oldMenu = event.getOldMenu();
        SysMenuPO newMenu = event.getNewMenu();
        String oldPath = oldMenu.getPath();
        String newPath = newMenu.getPath();
        // 更新子菜单的path
        List<SysMenuPO> childList = sysMenuService.list(new LambdaQueryWrapper<SysMenuPO>().select(SysMenuPO::getId,
                SysMenuPO::getPath).likeRight(SysMenuPO::getPath, oldPath + ","));
        for (SysMenuPO child : childList) {
            String oldChildPath = child.getPath();
            String newChildPath = newPath + oldChildPath.substring(oldPath.length());
            child.setPath(newChildPath);
        }
        sysMenuService.updateBatchById(childList);
    }
    
    @EventListener
    public void handleSysPostDeletedEvent(SysPostDeletedEvent event) {
        SysPostPO deletedPost = event.getDeletedPost();
        Long postId = deletedPost.getId();
        // 删除 用户-部门&职位绑定关系
        sysUserDeptPostReService.remove(new LambdaQueryWrapper<SysUserDeptPostRePO>().eq(SysUserDeptPostRePO::getPostId, postId));
    }
    
    @EventListener
    public void handleSysRoleDeletedEvent(SysRoleDeletedEvent event) {
        SysRolePO deletedRole = event.getDeletedRole();
        Long roleId = deletedRole.getId();
        // 删除 角色-菜单绑定关系
        sysRoleMenuReService.remove(new LambdaQueryWrapper<SysRoleMenuRePO>().eq(SysRoleMenuRePO::getRoleId, roleId));
        // 删除 角色-api资源 绑定关系
        sysRoleApiReService.remove(new LambdaQueryWrapper<SysRoleApiRePO>().eq(SysRoleApiRePO::getRoleId, roleId));
    }
    
    @EventListener
    public void handleSysUserAddedEvent(SysUserAddedEvent event) {
        SysUserPO addedUser = event.getAddedUser();
        Long userId = addedUser.getId();
        String tenant = addedUser.getTenant();
        // 初始化用户时，用户的可读、可写数据范围为  业务租户或者系统租户级
        String bizTenantOrSysTenant = CommonUtil.determineBizTenantOrSysTenant(tenant,
                tenantProperties.getBizTenantLevel());
        SysDeptPO sysDept = sysDeptService.getByPath(bizTenantOrSysTenant);
        Objects.requireNonNull(sysDept,
                "cannot get bizTenantOrSysTenant related deptId. deptPath -> " + bizTenantOrSysTenant);
        Integer deptId = sysDept.getId();
        
        List<SysDataScopePO> poList = new ArrayList<>(2);
        SysDataScopePO readDataScopePo = new SysDataScopePO();
        readDataScopePo.setId(IdWorker.getId());
        readDataScopePo.setTenant(bizTenantOrSysTenant);
        readDataScopePo.setType(DataScopeTypeEnum.READ);
        readDataScopePo.setUserId(userId);
        readDataScopePo.setDeptId(deptId);
        poList.add(readDataScopePo);
        
        SysDataScopePO updateDataScopePo = new SysDataScopePO();
        updateDataScopePo.setId(IdWorker.getId());
        updateDataScopePo.setTenant(bizTenantOrSysTenant);
        updateDataScopePo.setType(DataScopeTypeEnum.UPDATE);
        updateDataScopePo.setUserId(userId);
        updateDataScopePo.setDeptId(deptId);
        poList.add(updateDataScopePo);
        
        // 新增用户对应的数据操作域
        sysDataScopeService.saveBatch(poList);
        
        // 新增后台用户
        if (addedUser.getType() == UserTypeEnum.BACKEND) {
            SysBackendUserPO sysBackendUser = new SysBackendUserPO();
            sysBackendUser.setId(IdWorker.getId());
            sysBackendUser.setUserId(addedUser.getId());
            sysBackendUser.setSubstate(POSubstate.SysBackendUser.NORMAL.getSubstate());
            sysBackendUserService.save(sysBackendUser);
        }
    }
    
    @EventListener
    public void handleSysUserDeletedEvent(SysUserDeletedEvent event) {
        SysUserPO deletedUser = event.getDeletedUser();
        Long userId = deletedUser.getId();
        
        // 删除 用户-部门&职位绑定关系
        sysUserDeptPostReService.remove(new LambdaQueryWrapper<SysUserDeptPostRePO>().eq(SysUserDeptPostRePO::getUserId, userId));
        
        // 删除 用户-角色绑定关系
        sysUserRoleReService.remove(new LambdaQueryWrapper<SysUserRoleRePO>().eq(SysUserRoleRePO::getUserId, userId));
        
        // 删除后台用户
        if (deletedUser.getType() == UserTypeEnum.BACKEND) {
            sysBackendUserService.remove(new LambdaUpdateWrapper<SysBackendUserPO>()
                    .eq(SysBackendUserPO::getUserId, userId)
            );
        }
    }
    
    @EventListener
    public void handleSysUserLoginSuccessEvent(SysUserLoginSuccessEvent event) {
        SysUserPO loginUser = event.getSysUser();
        Long userId = loginUser.getId();
        // 清空缓存
        stringRedisTemplate.delete(CachePrefixEnum.AC_USER_TENANT.key(userId));
        stringRedisTemplate.delete(CachePrefixEnum.AC_USER_API.key(userId));
        stringRedisTemplate.delete(CachePrefixEnum.AC_USER_DATA_SCOPE.key(userId));
        // 重新加载缓存
        authService.queryUserTenant(CachePrefixEnum.AC_USER_TENANT, userId);
        authService.queryUserApi(CachePrefixEnum.AC_USER_API, userId);
        authService.queryUserDataScope(CachePrefixEnum.AC_USER_DATA_SCOPE, userId);
    }
    
    @EventListener
    public void handleSysUserUpdatedEvent(SysUserUpdatedEvent event) {
        SysUserPO oldUser = event.getOldUser();
        SysUserPO newUser = event.getNewUser();
        // 更新后台用户
        UserTypeEnum oldType = oldUser.getType();
        UserTypeEnum newType = newUser.getType();
        if (oldType != newType) {
            if (newType == UserTypeEnum.BACKEND) {
                SysBackendUserPO sysBackendUser = new SysBackendUserPO();
                sysBackendUser.setId(IdWorker.getId());
                sysBackendUser.setUserId(newUser.getId());
                sysBackendUser.setSubstate(POSubstate.SysBackendUser.NORMAL.getSubstate());
                sysBackendUserService.save(sysBackendUser);
            } else if (oldType == UserTypeEnum.BACKEND) {
                sysBackendUserService.remove(new LambdaUpdateWrapper<SysBackendUserPO>()
                        .eq(SysBackendUserPO::getUserId, oldUser.getId())
                );
            }
        }
    }
}
